﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

namespace EbnfStudio
{

    /// <summary>
    /// The rules for generating the graph tree
    /// </summary>
    class GraphTextual : IGraphTreeConfig, IGraphVisitor
    {

        #region IGraphTreeConfig

        /// <summary>
        /// Get size of the token
        /// </summary>
        public Size GetSize(Token token)
        {
            switch (token.Id)
            {
                case TokenClass.Literal:
                    return new Size(token.Lexeme.Length + 4, 1);
                case TokenClass.Identifier :
                    // include < and > ?
                    if (token.Data == 1) return new Size(token.Lexeme.Length + 4, 1);
                    return new Size(token.Lexeme.Length + 2, 1);
            }

            // defaulr
            return new Size(0, 0);
        }


        /// <summary>
        /// Get size of the token by class
        /// </summary>
        public Size GetSize(TokenClass id)
        {
            switch (id)
            {
                case TokenClass.Empty:
                case TokenClass.Assign:
                    return new Size(1, 1);
            }

            // no size.
            return new Size(0, 0);
        }


        /// <summary>
        /// Get size of the Syntax tree
        /// </summary>
        /// <param name="count">Number of rules within syntax tree</param>
        /// <param name="size">total size of all rules combined</param>
        /// <param name="max">maximum width and height of the contained rules</param>
        public Size GetSyntaxSize(int count, Size size, Size max)
        {
            /*
             * Rule
             * 
             * Rule
             */
            return new Size(max.Width, size.Height + (count - 1));
        }


        /// <summary>
        /// Get rule size
        /// </summary>
        /// <param name="maxIdSize">the maximum id size</param>
        /// <param name="exprSize">size of the contained expression</param>
        public Size GetRuleSize(Size maxIdSize, Size exprSize)
        {
            /*
             * Identifier = Expression;
             */
            return new Size(exprSize.Width + maxIdSize.Width + 3, exprSize.Height);
        }


        /// <summary>
        /// get size of the expression
        /// </summary>
        /// <param name="count">number terminals contained</param>
        /// <param name="size">Total size of the terminals</param>
        /// <param name="max">Maximum terminal size</param>
        /// <returns>Size of the expression block</returns>
        public Size GetExprSize(int count, Size size, Size max)
        {
            /*
             * Term->-Term
             */
            return new Size(size.Width + ((count - 1) * 3), max.Height);
        }


        /// <summary>
        /// Get size for the Term.
        /// </summary>
        /// <param name="count">Number of factors inside the terminal</param>
        /// <param name="size">Size of all factors combined</param>
        /// <param name="max">Max width and height of all terminals contained</param>
        public Size GetTermSize(int count, Size size, Size max)
        {
            // just one item
            if (count == 1) return new Size(max.Width, size.Height);

            /*
             * +->-factor->-+
             * |            |
             * +->-factor->-+
             */
            return new Size(max.Width + 8, size.Height + (count - 1));
        }


        /// <summary>
        /// Get size of the group
        /// </summary>
        /// <param name="size">Size of the containes expression</param>
        public Size GetGroupSize(Size size)
        {
            /*
             * Expression
             */
            return new Size(size.Width, size.Height);
        }


        /// <summary>
        /// Get size of the optional group
        /// </summary>
        /// <param name="size">size of the expression contained</param>
        public Size GetOptionalSize(Size size)
        {
            /*
             * +------->--------+
             * |                |
             * +->-Expression->-+
             */
            return new Size(size.Width + 8, size.Height + 2);
        }


        /// <summary>
        /// get size of the repetition group
        /// </summary>
        /// <param name="size">size of the expression contained</param>
        public Size GetRepetitionSize(Size size)
        {
            /*
             * +------->--------+
             * |                |
             * +-<-Expression-<-+
             */
            return new Size(size.Width + 8, size.Height + 2);
        }

        #endregion

        #region IGraphVisitor

        // the generated output
        string Output = "";

        // simple text buffer class
        class TextBuffer
        {
            // hold the data
            public List<StringBuilder> Data;


            // current line and column
            public int Line, Column;


            /// <summary>
            /// Set internal cursor position
            /// </summary>
            public void SetCursor(int line, int col)
            {
                Column = col; Line = line;
            }


            // print a char
            public void Print(char ch)
            {
                this[Line, Column] = ch;
                Column++;
            }

            // repeat the char
            public void Print(char ch, int count)
            {
                for (int i = 0; i < count; i++)
                    Print(ch);
            }


            // print a string
            public void Print(string str)
            {
                foreach (char ch in str)
                {
                    this[Line, Column] = ch;
                    Column++;
                }
            }


            /// <summary>
            /// Access like a bitmap
            /// </summary>
            public char this[int line, int col]
            {
                set
                {
                    // ensure line exists
                    while (Data.Count <= line) Data.Add(new StringBuilder());
                    if (Data[line] == null) Data[line] = new StringBuilder();

                    // ensure enough space in the string builder
                    var builder = Data[line];
                    if (builder.Length <= col)
                    {
                        builder.Append(' ', col - builder.Length+1);
                    }

                    // assign
                    builder[col] = value;
                }
            }


            /// <summary>
            /// Turn this into a string
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                var output = "";
                foreach (var b in Data)
                {
                    if (b == null) output += "\r\n";
                    else output += b.ToString() + "\r\n";
                }
                return output;
            }


            /// <summary>
            /// Create
            /// </summary>
            public TextBuffer()
            {
                Line = 0; Column = 0;
                Data = new List<StringBuilder>();
            }
        }

        // the buffer
        TextBuffer Buffer;

        // by default fowrard arrows ?
        bool ForwardArrows = true;

        /// <summary>
        /// Constructor
        /// </summary>
        public GraphTextual(AstSyntax syntax)
        {
            var visualyzer = new GraphTree((IGraphTreeConfig)this);
            syntax.Accept(visualyzer);
            visualyzer.SyntaxTree.Accept((IGraphVisitor)this);
        }


        /// <summary>
        /// Get text
        /// </summary>
        public override string ToString()
        {
            return Buffer.ToString();
        }


        /// <summary>
        /// syntax tree
        /// </summary>
        public void Visit(GraphSyntaxElement ast)
        {
            Buffer = new TextBuffer();

            int line = 0;

            // do each rule
            foreach (var graph in ast.Rules)
            {
                Buffer.SetCursor(line, 0);
                graph.Accept(this);
                line += graph.Size.Height + 1;
            }
        }


        /// <summary>
        /// rule
        /// </summary>
        public void Visit(GraphRuleElement ast)
        {
            var token = ast.Identifier.Identifier;
            if (token.Data == 1) Buffer.Print("<" + token.Lexeme + ">");
            else Buffer.Print(token.Lexeme);

            Buffer.Column = ast.Identifier.Size.Width-1;
            Buffer.Print("»─");
            ast.Expression.Accept(this);
            
        }


        /// <summary>
        /// expression
        /// </summary>
        public void Visit(GraphExpressionElement ast)
        {
            int cnt = ast.Terms.Count;
            for (int i = 0; i < cnt; i++)
            {
                // current line and position
                var line = Buffer.Line;
                var column = Buffer.Column;

                // display terminal
                var term = ast.Terms[i];
                term.Accept(this);

                // restore cursor position
                Buffer.SetCursor(line, column + term.Size.Width);

                // add a glue
                if (i < (cnt - 1))
                {
                    Buffer.Print("─" + Arrow(true) + "─");
                }
            }
        }


        /// <summary>
        /// terminal
        /// </summary>
        public void Visit(GraphTermElement ast)
        {
            // only a single
            if (ast.Factors.Count == 1)
            {
                // current line and position
                var line = Buffer.Line;
                var column = Buffer.Column;

                var factor = ast.Factors[0];
                factor.Accept(this);
                return;
            }
            
            /*
             * +->-factor->-+
             * |            |
             * +->-factor->-+
             */
            int cnt = ast.Factors.Count;
            for (int i = 0; i < cnt; i++)
            {
                // current line and position
                var line = Buffer.Line;
                var column = Buffer.Column;
                var factor = ast.Factors[i];
                var last = (i == (cnt - 1));
                var first = i == 0;

                if (last)       Buffer.Print("└─" + Arrow(true) + "─");
                else if (first) Buffer.Print("┬─" + Arrow(true) + "─");
                else            Buffer.Print("├─" + Arrow(true) + "─");

                // check if need to add any - characters
                var factorWidth = factor.Size.Width;
                var termWidth = ast.Size.Width - 8;
                var ad = 0;
                var cursorPos = 0;
                if (factorWidth < termWidth)
                {
                    ad = (termWidth - factorWidth);
                    if (ad % 2 != 0)
                    {
                        ad /= 2;
                        Buffer.Print('─', ad);
                    }
                    else
                    {
                        ad--;
                        ad /= 2;
                        Buffer.Print('─', ad + 1);
                    }
                    cursorPos = Buffer.Column;
                    ad++;
                }

                // evaluate the factor
                factor.Accept(this);

                // need to add some - characters after the factor expression
                if (ad > 0)
                {
                    Buffer.SetCursor(line, cursorPos + factorWidth);
                    Buffer.Print('─', ad);
                }

                // add ending
                Buffer.SetCursor(line, column + ast.Size.Width-4);

                if (last) Buffer.Print("─" + Arrow(true) + "─┘");
                else if (first) Buffer.Print("─" + Arrow(true) + "─┬");
                else Buffer.Print("─" + Arrow(true) + "─┤");

                // add | and |
                if (!last)
                {
                    Buffer.Line++;
                    for (int ln = 0; ln < factor.Size.Height; ln++)
                    {
                        Buffer[Buffer.Line, column] = '│';
                        Buffer[Buffer.Line, Buffer.Column - 1] = '│';
                        Buffer.Line++;
                    }
                    Buffer.Column = column;
                }
            }
        }


        /// <summary>
        /// factor
        /// </summary>
        public void Visit(GraphEmptyFactorElement ast)
        {
            Buffer.Print('─', ast.Size.Width);
        }


        /// <summary>
        /// identifier
        /// </summary>
        public void Visit(GraphIdentifierElement ast)
        {
            Buffer.Print('{');
            var token = ast.Identifier;
            if (token.Data == 1) Buffer.Print("<" + token.Lexeme + ">");
            else Buffer.Print(token.Lexeme);
            Buffer.Print('}');
        }


        /// <summary>
        /// literal
        /// </summary>
        public void Visit(GraphLiteralElement ast)
        {
            Buffer.Print('{');
            var token = ast.Literal;
            Buffer.Print((char)token.Data);
            Buffer.Print(token.Lexeme);
            Buffer.Print((char)token.Data);
            Buffer.Print('}');
        }


        /// <summary>
        /// group
        /// </summary>
        public void Visit(GraphGroupElement ast)
        {
            var line = Buffer.Line;
            var column = Buffer.Column;
            ast.Expression.Accept(this);
        }


        /// <summary>
        /// optional
        /// </summary>
        public void Visit(GraphOptionalElement ast)
        {
            // current line and column
            var line = Buffer.Line; var column = Buffer.Column;

            /*
             * +------->--------+
             * |                |
             * +->-Expression->-+
             */
            // top line
            int exprWidth = ast.Expression.Size.Width;
            Buffer.Print("┬───");
            if (exprWidth % 2 == 0)
            {
                var linePart = exprWidth / 2;
                Buffer.Print('─', linePart - 1); Buffer.Print(Arrow(true)); Buffer.Print('─', linePart);
            }
            else
            {
                var linePart = (exprWidth-1) / 2;
                Buffer.Print('─', linePart); Buffer.Print(Arrow(true)); Buffer.Print('─', linePart);
            }
            Buffer.Print("───┬");

            // divider line
            Buffer[line + 1, column] = '│'; Buffer[line + 1, column + exprWidth + 7] = '│';

            // the bottom line
            Buffer.SetCursor(line + 2, column);
            Buffer.Print("└─" + Arrow(true) + "─");

            // render th expression
            ast.Expression.Accept(this);

            // finish
            Buffer.SetCursor(line + 2, column + 4 + exprWidth);
            Buffer.Print("─" + Arrow(true) + "─┘");

        }


        /// <summary>
        /// repetition
        /// </summary>
        public void Visit(GraphRepetitionElement ast)
        {
            /*
             * +------->--------+
             * |                |
             * +-<-Expression-<-+
             */
            // current line and column
            var line = Buffer.Line; var column = Buffer.Column;

            // top line
            int exprWidth = ast.Expression.Size.Width;
            Buffer.Print("┬───");
            if (exprWidth % 2 == 0)
            {
                var linePart = exprWidth / 2;
                Buffer.Print('─', linePart - 1); Buffer.Print(Arrow(true)); Buffer.Print('─', linePart);
            }
            else
            {
                var linePart = (exprWidth - 1) / 2;
                Buffer.Print('─', linePart); Buffer.Print(Arrow(true)); Buffer.Print('─', linePart);
            }
            Buffer.Print("───┬");

            // divider line
            Buffer[line + 1, column] = '│'; Buffer[line + 1, column + exprWidth + 7] = '│';

            // the bottom line
            Buffer.SetCursor(line + 2, column);
            Buffer.Print("└─" + Arrow(false) + "─");

            // render th expression
            bool dirTmp = ForwardArrows;
            ForwardArrows = !ForwardArrows;
            ast.Expression.Accept(this);
            ForwardArrows = dirTmp;

            // finish
            Buffer.SetCursor(line + 2, column + 4 + exprWidth);
            Buffer.Print("─" + Arrow(false) + "─┘");
        }

        #endregion


        /// <summary>
        /// Get arrow
        /// </summary>
        string Arrow(bool forward)
        {
            var f = ForwardArrows ? forward : !forward;
            if (f) return "»";
            return "«";
        }
    }
}
